/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package au.com.domlogic.pvc.core.component;

import au.com.domlogic.pvc.core.component.ComponentMold.ComponentDecorator;
import au.com.domlogic.pvc.core.component.ComponentMold.Casting;
import au.com.domlogic.pvc.core.element.ElementMold;
import au.com.domlogic.pvc.core.element.PVCElement;
import au.com.domlogic.pvc.core.element.PVCElementDecorator;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;

/**
 * This mold allows the developer to shape a {@link DocumentFragment} outside of the
 * Document Context.
 *
 * Once the mold is instantiated and configured, developers can create 
 * {@link Casting}s from it by passing a {@link Document}.
 * Developers may also create {@link ComponentDecorator}s or {@link ElementDecorator}s
 * by passing {@link PVCComponent}s or {@link PVCElement}s repectively, to the
 * respective constructors.
 * 
 * @author Domenic Figliomeni
 */
public class ComponentMold implements Serializable {

    /**
     * The list of nested {@link ComponentMold}s.
     */
    private List<ComponentMold> componentMolds;

    /**
     * Default constructor which uses an {@link ArrayList} to hold nested
     * {@link ComponentMold}s.
     */
    public ComponentMold() {
        this.componentMolds = new ArrayList<ComponentMold>();
    }

    /**
     * Alternative constructor allows setting of an alternative data structure
     * for holding nested {@link ComponentMold}s.
     * @param molds
     */
    public ComponentMold(List<ComponentMold> molds) {
        this.componentMolds = molds;
    }

    /**
     * Mechanism for adding nested {@link ComponentMold}s. to this {@link ComponentMold}.
     * Conveniently returns this {@link ComponentMold} to allow for method chaining.
     * @param mold
     * @return this {@link ComponentMold}
     */
    public ComponentMold addComponentMold(ComponentMold mold) {
        this.getComponentMolds().add(mold);
        return this;
    }

    /**
     * @return the nested {@link ComponentMold}s.
     */
    public List<ComponentMold> getComponentMolds() {
        return componentMolds;
    }

    /**
     * @param componentModels the nested {@link ComponentMold}s to set.
     */
    public void setComponentModels(List<ComponentMold> componentModels) {
        if (componentModels != null) {
            this.componentMolds = componentModels;
        } else {
            this.componentMolds = new ArrayList<ComponentMold>();
        }
    }

    /**
     * This ConcreteComponent is a trivial implementation of a {@link PVCComponent}.
     */
    private class ConcreteComponent extends CompositeDocumentFragment implements PVCComponent {

        private ConcreteComponent(Document document) {
            this.setUnderlyingDocumentFragment(document.createDocumentFragment());
        }

        @Override
        public DocumentFragment gluePVC() {
            return this;
        }
    }

    /**
     * A {@link PVCComponent} based on this {@link ComponentMold}.
     */
    public class Casting extends CompositeDocumentFragment implements PVCComponent {

        public Casting(Document document) {
            PVCComponent component = new ConcreteComponent(document);
            //Decorate the Component with any Component Decorators in the contained molds
            for (ComponentMold mold : getComponentMolds()) {
                if (mold instanceof ElementMold) {
                    component = ((ElementMold) mold).new ComponentDecorator(component);
                } else {
                    component = mold.new ComponentDecorator(component);
                }
            }
            this.setUnderlyingDocumentFragment(component.gluePVC());
        }

        @Override
        public DocumentFragment gluePVC() {
            return this;
        }
    }

    /**
     * Decorator Pattern for a {@link PVCElement}.  Instances of this class will
     * decorate the {@link PVCElement} passed to the constructor by appending a
     * {@link PVCComponent} from this {@link ComponentMold} to it.
     */
    public class ElementDecorator extends PVCElementDecorator {

        private PVCComponent decoration;

        public ElementDecorator(PVCElement element) {
            super(element);
        }

        @Override
        protected void decorate() {
            this.decoration = new Casting(this.getOwnerDocument());
            this.appendChild(this.decoration);
        }

        /**
         * @return the decoration
         */
        public PVCComponent getDecoration() {
            return decoration;
        }
    }

    /**
     * Decorator Pattern for a {@link PVCComponent}.  Instances of this class will
     * decorate the {@link PVCComponent} passed to the constructor by appending a
     * {@link PVCComponent} from this {@link ComponentMold} to it.
     */
    public class ComponentDecorator extends PVCComponentDecorator {

        private PVCComponent decoration;

        public ComponentDecorator(PVCComponent component) {
            super(component);
        }

        @Override
        protected void decorate() {
            this.decoration = new Casting(this.getOwnerDocument());
            this.appendChild(this.decoration);
        }

        /**
         * @return the decoration
         */
        public PVCComponent getDecoration() {
            return decoration;
        }
    }
}
